package cc.rengu.oltp.utility.util.sm2;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.SecureRandom;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;

public class Sm2Signer {

    private Boolean forSigning;
    private ECKeyParameters key;
    private SecureRandom random;

    public String getAlgorithmName() {
        return "Sm2Signer";
    }

    public void init(boolean forSigning, CipherParameters parameters) throws InvalidKeyException {
        this.forSigning = forSigning;
        if (forSigning)// 签名
        {
            if (parameters instanceof ParametersWithRandom) {
                ParametersWithRandom rParam = (ParametersWithRandom) parameters;
                this.random = rParam.getRandom();
                parameters = rParam.getParameters();
            } else {
                this.random = new SecureRandom();
            }

            if (!(parameters instanceof ECPrivateKeyParameters)) {
                throw new InvalidKeyException("EC private key required for signing");
            }
            this.key = (ECPrivateKeyParameters) parameters;
        } else {// 验签
            if (!(parameters instanceof ECPublicKeyParameters)) {
                throw new InvalidKeyException("EC public key required for verification");
            }
            this.key = (ECPublicKeyParameters) parameters;
        }
    }

    public BigInteger[] generateSignature(byte[] message) throws Exception {
        if (!this.forSigning) {
            throw new Exception("not initialised for signing");
        }
        // 阶n
        BigInteger n = ((ECPrivateKeyParameters) this.key).getParameters().getN();
        int nBitLength = n.bitLength();
        // M_=ZA||M，e=Hv(M_)
        BigInteger e = new BigInteger(1, message);
        int eBitLength = e.bitLength();

        ECPrivateKeyParameters privKey = (ECPrivateKeyParameters) key;
        if (eBitLength > nBitLength) {
            throw new DataLengthException("input too large for ECNR key.");
        }
        BigInteger r = null;
        BigInteger s = null;
        do {//判断s
            BigInteger k;// 随机数 k∈[1,n-1], (x1,y1)=[k]G
            AsymmetricCipherKeyPair tempPair;
            do {// r=(e+x1) mod n
                //generate another, but very temporary, key pair using, the same EC parameters
                //生成随机数k，及(x1,y1),借用用户密钥对方式生成：PA=[dA]G={xA,yA}
                ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
                keyGen.init(new ECKeyGenerationParameters(privKey.getParameters(), this.random));
                tempPair = keyGen.generateKeyPair();
                k = ((ECPrivateKeyParameters) tempPair.getPrivate()).getD();
                ECPublicKeyParameters V = (ECPublicKeyParameters) tempPair.getPublic();
                BigInteger Vx = V.getQ().getAffineXCoord().toBigInteger();
                r = Vx.add(e).mod(n);
            } while (r.signum() == 0 || r.equals(BigInteger.ZERO) || r.add(k).equals(n));
            // s=((1/(1+dA)·(k-r·dA)) mod n
            BigInteger dA = privKey.getD(); // private key value

            BigInteger tmp = dA.add(BigInteger.ONE).modInverse(n);// 1/(1+dA)
            BigInteger tmp2 = k.subtract(r.multiply(dA));// (k-r·dA)
            s = tmp.multiply(tmp2).mod(n);
        } while (s.equals(BigInteger.ZERO));
        return new BigInteger[]{r, s};
    }

    public boolean verifySignature(byte[] message, BigInteger r, BigInteger s) throws Exception {
        if (this.forSigning) {
            throw new Exception("not initialised for verifying");
        }
        ECPublicKeyParameters pubKey = (ECPublicKeyParameters) key;
        // 阶n
        BigInteger n = pubKey.getParameters().getN();
        int nBitLength = n.bitLength();
        // M_'=ZA||M'，e'=Hv(M_')
        BigInteger e = new BigInteger(1, message);

        int eBitLength = e.bitLength();
        if (eBitLength > nBitLength) {
            throw new DataLengthException("input too large for ECNR key.");
        }
        // r'∈[1,n-1]
        if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(n) >= 0) {
            return false;
        }
        // s' ∈ [1,n-1] NB: ECNR spec says 0
        if (s.compareTo(BigInteger.ONE) < 0 || s.compareTo(n) >= 0) {
            return false;
        }

        // t=(r'+s') mod n
        BigInteger t = r.add(s).mod(n);
        //不用判断t?
        if (t.compareTo(BigInteger.ZERO) == 0) {
            return false;
        }
        // (x1',y1')=[s']G+[t]PA, compute P = sG + rW
        ECPoint G = pubKey.getParameters().getG();
        ECPoint W = pubKey.getQ();
        // calculate P using Bouncy math
        ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, t).normalize();
        if (P.isInfinity()) {
            return false;
        }
        //R=(e'+x1') mod n
        BigInteger x = P.getAffineXCoord().toBigInteger();
        BigInteger R = e.add(x).mod(n);
        return R.equals(r);
    }
}
